#include "observed_ptr.h"
#include "thread_pool.h"
#include "gtest/gtest.h"
#include <functional>
#include <iostream>

using crude::observed_ptr;
using crude::subject_ptr;
using crude::make_subject;

class observed_ptr_test : public testing::Test {};

namespace {
constexpr int PERSON_SIZE = 5;

const char *Names[PERSON_SIZE] = {"Tom", "Bob", "Jim", "Lily", "Jerry"};
class Person {
  static int count_;
  int age_;
  std::string name_;

public:
  static int Count() { return count_; }
  explicit Person(int age = 0, const char *name = "") : age_(age), name_(name) {
    count_++;
  }
  ~Person() { std::cout << __FUNCTION__ << --count_ << std::endl; }

  std::string GetName() { return name_.c_str(); }

  void SetName(const char *name) { name_ = name; }

  void SetAge(int age) { age_ = age; }

  int GetAge() { return age_; }
};
int Person::count_ = 0;

class Student {
  static int count_;
  int age_;
  std::string name_;
  ~Student() { std::cout << __FUNCTION__ << --count_ << std::endl; }
  std::unique_ptr<int> a;

public:
  static int Count() { return count_; }
  Student(int age = 0, const char *name = "") : age_(age), name_(name) {
    count_++;
  }

  void Release() {
    std::cout << __FUNCTION__;
    delete this;
  }

  std::string GetName() { return name_.c_str(); }

  int GetAge() { return age_; }

  char32_t t;
};
int Student::count_ = 0;
} // namespace

TEST_F(observed_ptr_test, subject_ptr) {
  // test int
  subject_ptr<int> sub_ptr(new int(2));
  ASSERT_TRUE((bool)sub_ptr);

  // test class
  ASSERT_EQ(0, Person::Count());
  {
    subject_ptr<Person> tom(new Person(10, "Tom"));
    ASSERT_EQ(1, Person::Count());

    // test operator->()
    ASSERT_EQ(10, tom->GetAge());
    ASSERT_EQ("Tom", tom->GetName());

    tom->SetAge(11);
    tom->SetName("New Tom");
    ASSERT_EQ(11, tom->GetAge());
    ASSERT_EQ("New Tom", tom->GetName());

    // test operator*()
    ASSERT_EQ(11, (*tom).GetAge());
    ASSERT_EQ("New Tom", (*tom).GetName());

    (*tom).SetAge(21);
    (*tom).SetName("New Tom 2");
    ASSERT_EQ(21, (*tom).GetAge());
    ASSERT_EQ("New Tom 2", (*tom).GetName());

    // test get()
    auto ptr = tom.get();
    ASSERT_EQ(21, ptr->GetAge());
    ASSERT_EQ("New Tom 2", ptr->GetName());

    ptr->SetAge(2);
    ptr->SetName("tom");
    ASSERT_EQ(2, tom->GetAge());
    ASSERT_EQ("tom", tom->GetName());

    // test move copy constructor
    subject_ptr<Person> p1(std::move(tom));
    ASSERT_TRUE((bool)p1);
    ASSERT_FALSE((bool)tom);
    ASSERT_EQ(2, p1->GetAge());
    ASSERT_EQ("tom", p1->GetName());

    // test move assign
    subject_ptr<Person> p2;
    p2 = std::move(p1);
    ASSERT_TRUE((bool)p2);
    ASSERT_FALSE((bool)p1);
    ASSERT_EQ(2, p2->GetAge());
    ASSERT_EQ("tom", p2->GetName());

    // test swap
    subject_ptr<Person> p3;
    p3.swap(p2);
    ASSERT_TRUE((bool)p3);
    ASSERT_FALSE((bool)p2);
    ASSERT_EQ(2, p3->GetAge());
    ASSERT_EQ("tom", p3->GetName());

    // test release
    auto p4 = p3.release();
    ASSERT_TRUE((bool)p4);
    ASSERT_FALSE((bool)p3);
    ASSERT_EQ(2, p4->GetAge());
    ASSERT_EQ("tom", p4->GetName());

    // test reset
    p3.reset(p4);
    ASSERT_TRUE((bool)p4);
    ASSERT_TRUE((bool)p3);
    ASSERT_EQ(2, p3->GetAge());
    ASSERT_EQ("tom", p3->GetName());

    p3.reset();
    ASSERT_FALSE((bool)p3);
    ASSERT_EQ(0, Person::Count());
  }
  ASSERT_EQ(0, Person::Count());
}

TEST_F(observed_ptr_test, SubjectPtr_Move) {
  subject_ptr<Person> sub_ptr(new Person());
  subject_ptr<Person> sub_ptr2(std::move(sub_ptr));
  ASSERT_TRUE(!(bool)sub_ptr);
  ASSERT_TRUE(sub_ptr2);
  subject_ptr<Person> sub_ptr3;
  ASSERT_TRUE(!(bool)sub_ptr3);
  sub_ptr3 = std::move(sub_ptr2);
  ASSERT_TRUE(!(bool)sub_ptr);
  ASSERT_TRUE(!(bool)sub_ptr2);
  ASSERT_TRUE(sub_ptr3);

  sub_ptr = std::move(sub_ptr2);
  ASSERT_TRUE(!(bool)sub_ptr);
  ASSERT_TRUE(!(bool)sub_ptr2);
}

TEST_F(observed_ptr_test, SubjectPtr_Release) {
  {
    subject_ptr<Person> sub_ptr(new Person());
    Person *person = sub_ptr.get();
    observed_ptr<Person> observer1(sub_ptr);
    observed_ptr<Person> observer2(sub_ptr);

    ASSERT_EQ(person, observer1.get());
    ASSERT_EQ(person, observer2.get());
    ASSERT_EQ(2, sub_ptr.get_observer_count());

    person = sub_ptr.release();
    ASSERT_EQ(nullptr, observer1.get());
    ASSERT_EQ(nullptr, observer2.get());
    ASSERT_EQ(0, sub_ptr.get_observer_count());
    delete person;
  }
  ASSERT_EQ(0, Person::Count());
}

TEST_F(observed_ptr_test, SubjectPtr_Reset) {
  subject_ptr<Person> sub_ptr(new Person());
  Person *person = sub_ptr.get();
  observed_ptr<Person> observer1(sub_ptr);
  observed_ptr<Person> observer2(sub_ptr);

  ASSERT_EQ(person, observer1.get());
  ASSERT_EQ(person, observer2.get());
  ASSERT_EQ(2, sub_ptr.get_observer_count());

  sub_ptr.reset();
  ASSERT_EQ(nullptr, observer1.get());
  ASSERT_EQ(nullptr, observer2.get());
  ASSERT_EQ(0, sub_ptr.get_observer_count());
  ASSERT_EQ(0, Person::Count());
}

TEST_F(observed_ptr_test, SubjectPtr_Move2) {
  {
    subject_ptr<Person> sub_ptr(new Person());
    Person *person = sub_ptr.get();
    observed_ptr<Person> observer1(sub_ptr);
    observed_ptr<Person> observer2(sub_ptr);

    ASSERT_EQ(person, observer1.get());
    ASSERT_EQ(person, observer2.get());
    ASSERT_EQ(2, sub_ptr.get_observer_count());

    subject_ptr<Person> sub_ptr2(std::move(sub_ptr));
    ASSERT_TRUE(!(bool)sub_ptr);
    ASSERT_TRUE(sub_ptr2);

    ASSERT_EQ(person, observer1.get());
    ASSERT_EQ(person, observer2.get());
    ASSERT_EQ(0, sub_ptr.get_observer_count());
    ASSERT_EQ(2, sub_ptr2.get_observer_count());

    subject_ptr<Person> sub_ptr3;
    ASSERT_TRUE(!(bool)sub_ptr3);
    sub_ptr3 = std::move(sub_ptr2);
    ASSERT_TRUE(!(bool)sub_ptr);
    ASSERT_TRUE(!(bool)sub_ptr2);
    ASSERT_TRUE(sub_ptr3);

    ASSERT_EQ(person, observer1.get());
    ASSERT_EQ(person, observer2.get());
    ASSERT_EQ(0, sub_ptr.get_observer_count());
    ASSERT_EQ(0, sub_ptr2.get_observer_count());
    ASSERT_EQ(2, sub_ptr3.get_observer_count());

    sub_ptr = std::move(sub_ptr2);
    ASSERT_TRUE(!(bool)sub_ptr);
    ASSERT_TRUE(!(bool)sub_ptr2);
  }
  ASSERT_EQ(0, Person::Count());
}

TEST_F(observed_ptr_test, ObserverPtr_Assign) {
  // int
  subject_ptr<int> sub_ptr(new int(2));
  observed_ptr<int> obs_ptr(sub_ptr);
  ASSERT_EQ(1, sub_ptr.get_observer_count());
  obs_ptr = obs_ptr;
  ASSERT_EQ(1, sub_ptr.get_observer_count());

  subject_ptr<int> sub_ptr2(new int(3));
  obs_ptr = observed_ptr<int>(sub_ptr2);
  ASSERT_EQ(0, sub_ptr.get_observer_count());
  ASSERT_EQ(1, sub_ptr2.get_observer_count());
}

TEST_F(observed_ptr_test, ObserverPtr_Move) {
  std::unique_ptr<int> u_ptr1(new int(1));
  auto u_ptr2 = std::move(u_ptr1);

  subject_ptr<int> sub_ptr(new int(2));
  observed_ptr<int> obs_ptr(sub_ptr);
  observed_ptr<int> obs_ptr2(std::move(obs_ptr));
  ASSERT_TRUE(!(bool)obs_ptr);
  ASSERT_TRUE(obs_ptr2);
}

class Subject {
public:
  Subject() {}
  int SomeMethod() { return 42; }
};

TEST_F(observed_ptr_test, Null) {
  observed_ptr<Subject> ptr;
  EXPECT_EQ(nullptr, ptr.get());
}

TEST_F(observed_ptr_test, LivesLonger) {
  auto pObs = crude::make_subject<Subject>();
  auto pObs2 = pObs.get();
  observed_ptr<Subject> ptr(pObs);
  EXPECT_NE(nullptr, ptr.get());
  EXPECT_EQ(1u, pObs.get_observer_count());
  pObs.release();
  delete pObs2;
  EXPECT_EQ(nullptr, ptr.get());
  EXPECT_NE(nullptr, pObs2);
}

TEST_F(observed_ptr_test, LivesShorter) {
  auto obs = make_subject<Subject>();
  {
    observed_ptr<Subject> ptr(obs);
    EXPECT_NE(nullptr, ptr.get());
    EXPECT_EQ(1u, obs.get_observer_count());
  }
  EXPECT_EQ(0u, obs.get_observer_count());
}

TEST_F(observed_ptr_test, CopyConstruct) {
  auto obs = make_subject<Subject>();
  {
    observed_ptr<Subject> ptr(obs);
    EXPECT_NE(nullptr, ptr.get());
    EXPECT_EQ(1u, obs.get_observer_count());
    {
      observed_ptr<Subject> ptr2(ptr);
      EXPECT_NE(nullptr, ptr2.get());
      EXPECT_EQ(2u, obs.get_observer_count());
    }
    EXPECT_EQ(1u, obs.get_observer_count());
  }
  EXPECT_EQ(0u, obs.get_observer_count());
}

TEST_F(observed_ptr_test, CopyAssign) {
  auto obs = make_subject<Subject>();
  {
    observed_ptr<Subject> ptr(obs);
    EXPECT_NE(nullptr, ptr.get());
    EXPECT_EQ(1u, obs.get_observer_count());
    {
      observed_ptr<Subject> ptr2;
      ptr2 = ptr;
      EXPECT_NE(nullptr, ptr2.get());
      EXPECT_EQ(2u, obs.get_observer_count());
    }
    EXPECT_EQ(1u, obs.get_observer_count());
  }
  EXPECT_EQ(0u, obs.get_observer_count());
}

TEST_F(observed_ptr_test, Vector) {
  auto obs = make_subject<Subject>();
  {
    std::vector<observed_ptr<Subject>> vec1;
    std::vector<observed_ptr<Subject>> vec2;
    vec1.emplace_back(obs);
    vec1.emplace_back(obs);
    EXPECT_NE(nullptr, vec1[0].get());
    EXPECT_NE(nullptr, vec1[1].get());
    EXPECT_EQ(2u, obs.get_observer_count());
    vec2 = vec1;
    EXPECT_NE(nullptr, vec2[0].get());
    EXPECT_NE(nullptr, vec2[1].get());
    EXPECT_EQ(4u, obs.get_observer_count());
    vec1.clear();
    EXPECT_EQ(2u, obs.get_observer_count());
    vec2.resize(10000);
    EXPECT_EQ(2u, obs.get_observer_count());
    vec2.resize(0);
    EXPECT_EQ(0u, obs.get_observer_count());
  }
  EXPECT_EQ(0u, obs.get_observer_count());
}

TEST_F(observed_ptr_test, VectorAutoClear) {
  std::vector<observed_ptr<Subject>> vec1;
  {
    auto obs = make_subject<Subject>();
    vec1.emplace_back(obs);
    vec1.emplace_back(obs);
    EXPECT_NE(nullptr, vec1[0].get());
    EXPECT_NE(nullptr, vec1[1].get());
    EXPECT_EQ(2u, obs.get_observer_count());
  }
  EXPECT_EQ(nullptr, vec1[0].get());
  EXPECT_EQ(nullptr, vec1[1].get());
}

TEST_F(observed_ptr_test, ResetNull) {
  auto obs = make_subject<Subject>();
  observed_ptr<Subject> ptr(obs);
  EXPECT_EQ(1u, obs.get_observer_count());
  ptr.reset();
  EXPECT_EQ(0u, obs.get_observer_count());
}

TEST_F(observed_ptr_test, Reset) {
  auto obs1 = make_subject<Subject>();
  auto obs2 = make_subject<Subject>();
  observed_ptr<Subject> ptr(obs1);
  EXPECT_EQ(1u, obs1.get_observer_count());
  EXPECT_EQ(0u, obs2.get_observer_count());
  ptr = obs2;
  EXPECT_EQ(0u, obs1.get_observer_count());
  EXPECT_EQ(1u, obs2.get_observer_count());
}

TEST_F(observed_ptr_test, Equals) {
  auto obj1 = make_subject<Subject>();
  auto obj2 = make_subject<Subject>();
  observed_ptr<Subject> null_ptr1;
  observed_ptr<Subject> obj1_ptr1(obj1);
  observed_ptr<Subject> obj2_ptr1(obj2);
  {
    observed_ptr<Subject> null_ptr2;
    EXPECT_TRUE(null_ptr1 == null_ptr2);

    observed_ptr<Subject> obj1_ptr2(obj1);
    EXPECT_TRUE(obj1_ptr1 == obj1_ptr2);

    observed_ptr<Subject> obj2_ptr2(obj2);
    EXPECT_TRUE(obj2_ptr1 == obj2_ptr2);
  }
  EXPECT_FALSE(null_ptr1 == obj1_ptr1);
  EXPECT_FALSE(null_ptr1 == obj2_ptr1);
  EXPECT_FALSE(obj1_ptr1 == obj2_ptr1);
}

TEST_F(observed_ptr_test, NotEquals) {
  auto obj1 = make_subject<Subject>();
  auto obj2 = make_subject<Subject>();
  observed_ptr<Subject> null_ptr1;
  observed_ptr<Subject> obj1_ptr1(obj1);
  observed_ptr<Subject> obj2_ptr1(obj2);
  {
    observed_ptr<Subject> null_ptr2;
    observed_ptr<Subject> obj1_ptr2(obj1);
    observed_ptr<Subject> obj2_ptr2(obj2);
    EXPECT_FALSE(null_ptr1 != null_ptr2);
    EXPECT_FALSE(obj1_ptr1 != obj1_ptr2);
    EXPECT_FALSE(obj2_ptr1 != obj2_ptr2);
  }
  EXPECT_TRUE(null_ptr1 != obj1_ptr1);
  EXPECT_TRUE(null_ptr1 != obj2_ptr1);
  EXPECT_TRUE(obj1_ptr1 != obj2_ptr1);
}

TEST_F(observed_ptr_test, Bool) {
  auto obj1 = make_subject<Subject>();
  observed_ptr<Subject> null_ptr;
  observed_ptr<Subject> obj1_ptr(obj1);
  bool null_bool = !!null_ptr;
  bool obj1_bool = !!obj1_ptr;
  EXPECT_FALSE(null_bool);
  EXPECT_TRUE(obj1_bool);
}

TEST_F(observed_ptr_test, observed_from_this) {
  class Subject2 : public crude::enable_observed_from_this<Subject2> {};

  auto obj1 = make_subject<Subject2>();
  {
    observed_ptr<Subject2> null_ptr;
    observed_ptr<Subject2> obj1_ptr(obj1);
    bool null_bool = !!null_ptr;
    bool obj1_bool = !!obj1_ptr;
    EXPECT_FALSE(null_bool);
    EXPECT_TRUE(obj1_bool);
    ASSERT_EQ(2, obj1.get_observer_count());

    obj1_ptr = obj1->observe_from_this();
    ASSERT_EQ(2, obj1.get_observer_count());
    auto obj1_ptr2 = obj1->observe_from_this();
    ASSERT_EQ(3, obj1.get_observer_count());
  }

  ASSERT_EQ(1, obj1.get_observer_count());
}


TEST_F(observed_ptr_test, Thread_subject_ptr) {
  auto AddObserver = [](const subject_ptr<Subject> &obj) {
    std::vector<observed_ptr<Subject>> obrs;
    for (int i = 0; i < 10; i++) {
      obrs.push_back(observed_ptr<Subject>(obj));
    }
  };

  auto obj1 = crude::make_subject<Subject>();

  crude::ThreadPool pool(4);
  for (int i = 0; i < 10; i++) {
    pool.async(AddObserver, std::ref(obj1));
  }

  pool.wait();

  ASSERT_EQ(0, obj1.get_observer_count());
}

template <class Func, class... Args>
void process(Func &&func, Args &&... args) {
  auto start = std::chrono::system_clock::now();
  std::forward<Func>(func)(std::forward<Args>(args)...);
  std::cout << "count time : "
            << std::chrono::duration<double>(std::chrono::system_clock::now() -
                                             start)
                   .count()
            << "\n";
}
